using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using Opc.Ua;
using Opc.Ua.Client;
using Opc.Ua.Client.ComplexTypes;
using Opc.Ua.Client.Controls;
using static System.Windows.Forms.VisualStyles.VisualStyleElement;

namespace OpcClientTest
{
    public partial class Form1 : Form
    {
        private ApplicationConfiguration m_configuration;
        private Session m_session;
        private SessionReconnectHandler m_reconnectHandler;

        /// <summary>
        /// Default session values.
        /// </summary>
        public static readonly uint DefaultSessionTimeout = 60000;
        public static readonly int DefaultDiscoverTimeout = 15000;
        public static readonly int DefaultReconnectPeriod = 1;
        public static readonly int DefaultReconnectPeriodExponentialBackOff = 10;
        private string SessionName;
        private bool m_connectedOnce;

        private NodeId Node { get; set; }
        /// <summary>
        /// The discover timeout in ms.
        /// </summary>
        public int DiscoverTimeout { get; set; } = DefaultDiscoverTimeout;

        /// <summary>
        /// The user identity to use when creating the session.
        /// </summary>
        public IUserIdentity UserIdentity { get; set; }
        /// <summary>
        /// The locales to use when creating the session.
        /// </summary>
        public string[] PreferredLocales { get; set; }
        /// <summary>
        /// The number of seconds between reconnect attempts (0 means reconnect is disabled).
        /// </summary>
        public int ReconnectPeriod { get; set; } = DefaultReconnectPeriod;

        #region EventHandler
        private EventHandler m_ReconnectComplete;
        private EventHandler m_ReconnectStarting;
        private EventHandler m_KeepAliveComplete;
        private EventHandler m_ConnectComplete;
        /// <summary>
        /// Raised when a good keep alive from the server arrives.
        /// </summary>
        public event EventHandler KeepAliveComplete
        {
            add { m_KeepAliveComplete += value; }
            remove { m_KeepAliveComplete -= value; }
        }

        /// <summary>
        /// Raised when a reconnect operation starts.
        /// </summary>
        public event EventHandler ReconnectStarting
        {
            add { m_ReconnectStarting += value; }
            remove { m_ReconnectStarting -= value; }
        }

        /// <summary>
        /// Raised when a reconnect operation completes.
        /// </summary>
        public event EventHandler ReconnectComplete
        {
            add { m_ReconnectComplete += value; }
            remove { m_ReconnectComplete -= value; }
        }

        /// <summary>
        /// Raised after successfully connecting to or disconnecing from a server.
        /// </summary>
        public event EventHandler ConnectComplete
        {
            add { m_ConnectComplete += value; }
            remove { m_ConnectComplete -= value; }
        }
        #endregion

        public Form1(ApplicationConfiguration configuration)
        {
            InitializeComponent();
            this.Icon = ClientUtils.GetAppIcon();
            m_configuration = configuration;
            SessionName = "OpcClientTestSession";
        }

        private async void button1_Click(object sender, EventArgs e)
        {
            await Connect(textBox1.Text, false);

            // browse the instances in the server.
            browseTreeViewCtrl1.Initialize(m_session, ObjectIds.ObjectsFolder, ReferenceTypeIds.Organizes, ReferenceTypeIds.Aggregates);

            browseTreeViewCtrl1.NodeDoubleClick += new EventHandler(NodeDbClick);
        }

        /// <summary>
        /// Creates a new session.
        /// </summary>
        /// <returns>The new session object.</returns>
        public async Task<Session> Connect(
            string serverUrl,
            bool useSecurity,
            uint sessionTimeout = 0)
        {
            // disconnect from existing session.
            InternalDisconnect();

            // select the best endpoint.
            var endpointDescription = CoreClientUtils.SelectEndpoint(m_configuration, serverUrl, useSecurity, DiscoverTimeout);
            var endpointConfiguration = EndpointConfiguration.Create(m_configuration);
            var endpoint = new ConfiguredEndpoint(null, endpointDescription, endpointConfiguration);

            try
            {
                //m_session = await Session.Create(m_configuration, (ITransportWaitingConnection)null, endpoint, false, false, m_configuration.ApplicationName, 15000, UserIdentity, PreferredLocales, default(CancellationToken));
                m_session = await Session.Create(
                    m_configuration,
                    endpoint,
                    false,
                    false,
                    (String.IsNullOrEmpty(SessionName)) ? m_configuration.ApplicationName : SessionName,
                    sessionTimeout == 0 ? DefaultSessionTimeout : sessionTimeout,
                    UserIdentity,
                PreferredLocales);

                // set up keep alive callback.
                m_session.KeepAlive += new KeepAliveEventHandler(Session_KeepAlive);

                // set up reconnect handler.
                m_reconnectHandler = new SessionReconnectHandler(true, DefaultReconnectPeriodExponentialBackOff * 1000);

                // raise an event.
                DoConnectComplete(null);

                var typeSystemLoader = new ComplexTypeSystem(m_session);
                await typeSystemLoader.Load();
            }
            catch (Exception e)
            {
                Utils.LogError(e, "Failed to load complex type system.");
            }

            // return the new session.
            return m_session;
        }
        /// <summary>
        /// Disconnects from the server.
        /// </summary>
        private void InternalDisconnect()
        {
            // stop any reconnect operation.
            if (m_reconnectHandler != null)
            {
                m_reconnectHandler.Dispose();
                m_reconnectHandler = null;
            }

            // disconnect any existing session.
            if (m_session != null)
            {
                m_session.KeepAlive -= Session_KeepAlive;
                m_session.Close(10000);
                m_session = null;
            }

            // raise an event.
            DoConnectComplete(null);
        }
        /// <summary>
        /// Raises the connect complete event on the main GUI thread.
        /// </summary>
        private void DoConnectComplete(object state)
        {
            if (m_ConnectComplete != null)
            {
                m_ConnectComplete(this, null);
            }
        }

        /// <summary>
        /// Handles a keep alive event from a session.
        /// </summary>
        private void Session_KeepAlive(ISession session, KeepAliveEventArgs e)
        {
            try
            {
                // check for events from discarded sessions.
                if (!Object.ReferenceEquals(session, m_session))
                {
                    return;
                }

                // start reconnect sequence on communication error.
                if (ServiceResult.IsBad(e.Status))
                {
                    if (ReconnectPeriod <= 0)
                    {
                        return;
                    }

                    var state = m_reconnectHandler.BeginReconnect(m_session, ReconnectPeriod * 1000, Server_ReconnectComplete);
                    if (state == SessionReconnectHandler.ReconnectState.Triggered)
                    {
                        m_ReconnectStarting?.Invoke(this, e);
                    }

                    return;
                }

                // raise any additional notifications.
                if (m_KeepAliveComplete != null)
                {
                    m_KeepAliveComplete(this, e);
                }
            }
            catch (Exception exception)
            {
                Console.WriteLine(exception.Message);
            }
        }

        /// <summary>
        /// Handles a reconnect event complete from the reconnect handler.
        /// </summary>
        private void Server_ReconnectComplete(object sender, EventArgs e)
        {
            try
            {
                // ignore callbacks from discarded objects.
                if (!Object.ReferenceEquals(sender, m_reconnectHandler))
                {
                    return;
                }

                // only apply session if reconnect was required
                if (m_reconnectHandler.Session != null)
                {
                    if (!ReferenceEquals(m_session, m_reconnectHandler.Session))
                    {
                        var session = m_session;
                        session.KeepAlive -= Session_KeepAlive;
                        m_session = m_reconnectHandler.Session as Session;
                        m_session.KeepAlive += Session_KeepAlive;
                        Utils.SilentDispose(session);
                    }
                }

                // raise any additional notifications.
                if (m_ReconnectComplete != null)
                {
                    m_ReconnectComplete(this, e);
                }
            }
            catch (Exception exception)
            {
                Console.WriteLine(exception.Message);
            }
        }

        private void button2_Click(object sender, EventArgs e)
        {
            WriteHandler(null, null);
        }
        bool isRead = false;
        /// <summary>
        /// 点位选定事件
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="e"></param>
        private void NodeDbClick(object obj, EventArgs e)
        {
            isRead = false;
            Thread.Sleep(1000);
            isRead = true;
            NodeId node = (NodeId)obj;
            if (m_session == null || node == null) return;

            Node = node;
            initWriteConfig(node);

            attributesListViewCtrl1.ChangeSession(m_session);
            //attributesListViewCtrl1.ReadAttributes(node, true);
            Task.Factory.StartNew(() => {
                while (isRead)
                {
                    attributesListViewCtrl1.ReadAttributes(node, false);
                    Thread.Sleep(400);
                }
            });
        }
        #region 写入
        /// <summary>
        /// 初始化写参数
        /// </summary>
        uint writeAttributeId = 13;
        private void initWriteConfig(NodeId nodeId)
        {
            ReadValueIdCollection nodesToWrite = new ReadValueIdCollection();
            //加载点位所有属性
            foreach (uint readAttributeId in Opc.Ua.Attributes.GetIdentifiers())
            {
                ReadValueId nodeToRead = new ReadValueId();
                nodeToRead.NodeId = nodeId;
                nodeToRead.AttributeId = readAttributeId;
                nodesToWrite.Add(nodeToRead);
            }

            // read the attributes.
            DataValueCollection results = null;
            DiagnosticInfoCollection diagnosticInfos = null;

            m_session.Read(
                null,
                0,
                TimestampsToReturn.Neither,
                nodesToWrite,
                out results,
                out diagnosticInfos);

            //记录值属性
            for (int ii = 0; ii < results.Count; ii++)
            {
                // check for error.
                if (StatusCode.IsBad(results[ii].StatusCode))
                {
                    if (results[ii].StatusCode == StatusCodes.BadAttributeIdInvalid)
                    {
                        continue;
                    }
                }

                // add the metadata for the attribute.
                writeAttributeId = nodesToWrite[ii].AttributeId;

                string name = Attributes.GetBrowseName(writeAttributeId);
                if (name == "Value")
                {
                    return;
                }
            }
        }

        /// <summary>
        /// Reads the values displayed in the control and moves to the display results state.
        /// </summary>
        public void WriteHandler(object obj, EventArgs e)
        {
            // build list of values to write.
            WriteValueCollection writeValues = CreateWaferId();
            WriteValue(writeValues);
        }

        /// <summary>
        /// 写入值
        /// </summary>
        /// <exception cref="ServiceResultException"></exception>
        private void WriteValue(WriteValueCollection writeCollection)
        {
            if (writeCollection == null) return;
            if (m_session == null)
            {
                throw new ServiceResultException(StatusCodes.BadNotConnected);
            }

            // read the values.
            StatusCodeCollection results = null;
            DiagnosticInfoCollection diagnosticInfos = null;

            var header = m_session.Write(
                null,
                writeCollection,
                out results,
                out diagnosticInfos);

            ClientBase.ValidateResponse(results, writeCollection);
            ClientBase.ValidateDiagnosticInfos(diagnosticInfos, writeCollection);
        }

        WriteValueCollection nodesToWrite { get; set; }
        /// <summary>
        /// 生产WaferID
        /// </summary>
        /// <param name="count"></param>
        /// <returns></returns>
        private WriteValueCollection CreateWaferId(int count = 120)
        {
            nodesToWrite = new WriteValueCollection();
            int idx = 0;
            List<string> wafers = new List<string>();
            while (idx < count)
            {
                wafers.Add( $"T{DateTimeOffset.Now.ToUnixTimeSeconds() + idx}");
                idx++;
            }
            WriteValue writeValue = CreateWriteValue(wafers.ToArray());
            nodesToWrite.Add(writeValue);
            return nodesToWrite;
        }

        /// <summary>
        /// 创建一个定稿值
        /// </summary>
        /// <param name="value"></param>
        /// <param name="indx"></param>
        /// <returns></returns>
        private WriteValue CreateWriteValue(object value)
        {
            DataValue dataValue = CreateDataValue(value);

            WriteValue writeValue = new WriteValue();
            writeValue.NodeId = Node;
            writeValue.AttributeId = Attributes.Value;
            writeValue.Value = dataValue;
            writeValue.Value.WrappedValue = new Variant(value);

            return writeValue;
        }
        /// <summary>
        /// 创建一个值
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        private DataValue CreateDataValue(object value)
        {
            Variant variant = new Variant(value);
            DataValue dataValue = new DataValue(variant);
            return dataValue;
        }
        #endregion
    }
}
